package cn.lau.demo.auth;

import cn.lau.demo.pojo.User;
import cn.lau.demo.service.UserService;
import cn.lau.demo.util.JWTUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// @Component
// 注意！！！此类不能使用@Component注解，因为在ShiroConfig配置类中也需要使用@Bean注入该类对象，后果就是credentialsMatcher属性值设置后依然使用默认的SimpleCredentialsMatcher类对象
public class AuthRealm extends AuthorizingRealm {

	private final static Logger LOGGER = LoggerFactory.getLogger(AuthRealm.class);

	@Autowired
	private UserService userService;

	/**
	 * 大坑！，必须重写此方法，不然Shiro会报错
	 */
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof JWTToken;
	}

	/**
	 * 授权
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		User user = (User) principals.getPrimaryPrincipal();

		LOGGER.info("用户：{}执行授权", user.getUsername());

		/* 此处应执行业务层逻辑获取该用户的角色及权限集合 */

		info.addRoles(user.getRoles());
		info.addStringPermissions(user.getPerms());

		return info;
	}

	/**
	 * 认证
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		JWTToken jwtToken = (JWTToken) token;
		User user;
		String username = (String) jwtToken.getPrincipal();
		if (username == null) {  // JWTToken(username, token)系统自动登录
			// 可能是带token访问，检查credentials
			username = JWTUtil.getUsername(jwtToken.getCredentials().toString());
			if (username == null) {
				throw new AuthenticationException("非法令牌！");
			}
			user = userService.getByUsername(username);
			// 在JWTToken的credentials字段为Token的情况下，返回时构建的SimpleAuthenticationInfo对象的user.getPassword()会跟JWTToken.getCredentials()进行匹配比较，很显然这样是不会通过的。
			// 那只能改一改JWTToken的credentials字段为user.getPassword
			jwtToken.setCredentials(user.getPassword());
		} else {    // JWTToken(username, password)登录
			user = userService.getByUsername(username);
			if (user == null) {
				LOGGER.debug("未知用户：{}", username);
				throw new UnknownAccountException("未知用户");
			}
			if (user.getLocked()) {
				LOGGER.debug("已锁定用户尝试登录：{}", username);
				throw new LockedAccountException("用户已锁定");
			}
		}
		// SimpleAuthenticationInfo(principal, hashedCredentials, ByteSource.Util.bytes(salt), realmName)
		return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
	}
}
